package com.sinitek.model.service.impl;

import com.alibaba.fastjson.JSON;
import com.sinitek.enums.AggregationEnum;
import com.sinitek.model.query.MustNotQuery;
import com.sinitek.model.query.MustQuery;
import com.sinitek.model.query.ShouldQuery;
import com.sinitek.model.service.BaseEsService;
import com.sinitek.page.Page;
import com.sinitek.web.request.BasePageRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: elastic
 * @description:
 * @author: Mr.chen
 * @create: 2019-04-26 13:34
 */
@Component
public class BaseEsServiceImpl<T> implements BaseEsService<T> {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 数据装载
     *
     * @param hits
     * @return
     */
    private List<Map<String, Object>> getHitList(SearchHits hits) {
        System.out.println();
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map;
        for (SearchHit searchHit : hits) {
            map = new HashMap<>();
            // 处理源数据
            map.put("source", searchHit.getSourceAsMap());
            list.add(map);
        }
        return list;
    }

    /**
     * 数据装载(高亮)
     *
     * @param hits
     * @return
     */
    private List<Map<String, Object>> getHitListHighlight(SearchHits hits) {
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map;
        for (SearchHit searchHit : hits) {
            map = new HashMap<>();
            // 处理源数据
            map.put("source", searchHit.getSourceAsMap());
            // 处理高亮数据
            Map<String, Object> hitMap = new HashMap<>();
            searchHit.getHighlightFields().forEach((k, v) -> {
                String hight = "";
                for (Text text : v.getFragments()) hight += text.string();
                hitMap.put(v.getName(), hight);
            });
            map.put("highlight", hitMap);
            list.add(map);
        }
        return list;
    }

    /**
     * 构造高亮器
     *
     * @param fieldNames
     * @return
     */
    private HighlightBuilder createHighlightBuilder(String... fieldNames) {
        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .preTags("<span style='color:red'>")
                .postTags("</span>");
        // 设置高亮字段
        for (String fieldName : fieldNames) highlightBuilder.field(fieldName);
        return highlightBuilder;
    }

    /**
     * 通过编号删除文档
     *
     * @param indexName (索引名)
     * @param type      (类型名)
     * @param _id       (_id)
     * @return
     */
    @Override
    public Boolean deleteById(String indexName, String type, String _id) {
        DeleteResponse deleteResponse = elasticsearchTemplate.getClient().prepareDelete(indexName, type, _id).execute().actionGet();
        String result = deleteResponse.getResult().getLowercase();
        if (!"deleted".equalsIgnoreCase(result)) {
            return false;
        }
        return true;
    }

    /**
     * 更新文档
     *
     * @param t         (对象)
     * @param indexName (索引名)
     * @param type      (类型名)
     * @param _id       (_id)
     * @return
     */
    @Override
    public T update(T t, String indexName, String type, String _id) {
        String jsonObj = JSON.toJSONString(t);
        UpdateRequestBuilder updateRequestBuilder = elasticsearchTemplate.getClient().prepareUpdate(indexName, type, _id);
        UpdateResponse updateResponse = updateRequestBuilder.setDoc(jsonObj, XContentType.JSON).get();
        String result = updateResponse.getResult().getLowercase();
        if ("updated".equalsIgnoreCase(result)) {
            return t;
        }
        return null;
    }

    /**
     * 保存文档
     *
     * @param t         (对象)
     * @param indexName (索引名)
     * @param type      (类型名)
     * @return
     */
    @Override
    public T save(T t, String indexName, String type) {
        String jsonObj = JSON.toJSONString(t);
        IndexRequestBuilder indexRequestBuilder = elasticsearchTemplate.getClient().prepareIndex(indexName, type);
        IndexResponse response = indexRequestBuilder.setSource(jsonObj, XContentType.JSON).get();
        String result = response.getResult().getLowercase();
        if ("created".equalsIgnoreCase(result)) {
            return t;
        }
        return null;
    }

    /**
     * 通过文档编号查询
     *
     * @param id
     * @return
     */
    @Override
    public T findById(String indexName, String type, String id) {
        GetResponse getResponse = elasticsearchTemplate.getClient().prepareGet(indexName, type, id).setFetchSource(null, "data").execute().actionGet();
        Map<String, Object> source = getResponse.getSource();
        String jsonString = JSON.toJSONString(source);
        Object result = JSON.parseObject(jsonString, Object.class);
        return (T) result;
    }

    /**
     * 索引查询 (模糊匹配)
     *
     * @param indexName     (文档索引名)
     * @param sortFieldName (排序字段名)
     * @param sortOrder     (排序方式)
     * @param page          (分页对象)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllMatch(String indexName, String sortFieldName, String sortOrder, BasePageRequest page) {
        MatchAllQueryBuilder query = QueryBuilders.matchAllQuery();
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 关键词查询 (模糊匹配)
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param searchFieldName (检索的字段名称)
     * @param page            (分页对象)
     * @param keyWord         (搜索关键词)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllMatchByKeyword(String indexName, String sortFieldName, String sortOrder, String searchFieldName, BasePageRequest page, String keyWord) {
        QueryBuilder query = QueryBuilders.matchPhraseQuery(searchFieldName, keyWord);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 多关键词匹配某一字段查询
     * 通过索引名称、排序字段、关键词、分页参数获取文档集合(分词精确查询)
     * 只要满足一个就会返回。但是不会返回二者的并集。
     * ("key", obj) 完全匹配、("key", obj1, obj2..)一次匹配多个值
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param searchFieldName 检索的字段名称
     * @param page            (分页对象)
     * @param keyWords        (搜索多个关键词)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllTermsByKeywords(String indexName, String sortFieldName, String sortOrder, String searchFieldName, BasePageRequest page, String... keyWords) {
        QueryBuilder query = QueryBuilders.termsQuery(searchFieldName, keyWords);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 通过索引名称、排序字段、关键词、分页参数获取文档集合(分词精确查询)
     * 一种略高级的查询，充分考虑了stop-word的低优先级，提高了查询精确性。
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param searchFieldName (搜索的字段名称)
     * @param page            (分页对象)
     * @param keyword         (搜索关键词)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllCommonTermsByKeywords(String indexName, String sortFieldName, String sortOrder, String searchFieldName, BasePageRequest page, Object keyword) {
        QueryBuilder query = QueryBuilders.commonTermsQuery(searchFieldName, keyword);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 多字段匹配关键词查询 (模糊匹配)
     *
     * @param indexName        (文档索引名)
     * @param sortFieldName    (排序字段名)
     * @param sortOrder        (排序方式)
     * @param keyWord          (搜索关键词)
     * @param page             (分页对象)
     * @param searchFieldNames (搜索多个字段名称)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllMultiMatchByKeywords(String indexName, String sortFieldName, String sortOrder, String keyWord, BasePageRequest page, String... searchFieldNames) {
        //设置高亮
        HighlightBuilder highlightBuilder = createHighlightBuilder(searchFieldNames);

        QueryBuilder query = QueryBuilders.multiMatchQuery(keyWord, searchFieldNames);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .highlighter(highlightBuilder)
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        //高亮数据装载
        pageResult.setData(getHitListHighlight(hits));
        return pageResult;
    }

    /**
     * 前缀关键词查询 Gar* (模糊匹配)
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param page            (分页对象)
     * @param searchFieldName (搜索的字段名称)
     * @param prefix          (关键词前缀)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllByPrefix(String indexName, String sortFieldName, String sortOrder, BasePageRequest page, String searchFieldName, String prefix) {
        QueryBuilder query = QueryBuilders.prefixQuery(searchFieldName, prefix);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 重要 - 关键词模糊查询 (模糊匹配)
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param page            (分页对象)
     * @param searchFieldName (搜索的字段名称)
     * @param keyword         (搜索关键词)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllFuzzyByKeyWord(String indexName, String sortFieldName, String sortOrder, BasePageRequest page, String searchFieldName, String keyword) {
        QueryBuilder query = QueryBuilders.fuzzyQuery(searchFieldName, keyword);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 重要 - 关键词通配符查询 * (模糊匹配)
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param page            (分页对象)
     * @param searchFieldName (搜索的字段名称)
     * @param keyword         (搜索关键词)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllWildcardByKeyWord(String indexName, String sortFieldName, String sortOrder, BasePageRequest page, String searchFieldName, String keyword) {
        QueryBuilder query = QueryBuilders.wildcardQuery(searchFieldName, keyword);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 重要 - 范围查询包含(模糊匹配)
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param page            (分页对象)
     * @param searchFieldName (搜索的字段名称)
     * @param from            (搜索开始值 10001)
     * @param to              (搜索结束值 10004 结果包含 10001、10002、10003、10004)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllRangeIncludeByCondition(String indexName, String sortFieldName, String sortOrder, BasePageRequest page, String searchFieldName, Object from, Object to) {
        QueryBuilder query = QueryBuilders.rangeQuery(searchFieldName).from(from).to(to);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 重要 - 范围查询不包含 (模糊匹配)
     *
     * @param indexName       (文档索引名)
     * @param sortFieldName   (排序字段名)
     * @param sortOrder       (排序方式)
     * @param page            (分页对象)
     * @param searchFieldName (搜索的字段名称)
     * @param from            (搜索开始值 10001)
     * @param to              (搜索结束值 10004 结果包含 10002、10003)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllRangeUnIncludeByCondition(String indexName, String sortFieldName, String sortOrder, BasePageRequest page, String searchFieldName, Object from, Object to) {
        QueryBuilder query = QueryBuilders.rangeQuery(searchFieldName).gt(from).lt(to);
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }

    /**
     * 重要 - 组合查询(精准匹配)
     *
     * @param indexName     (文档索引名)
     * @param sortFieldName (排序字段名)
     * @param sortOrder     (排序方式)
     * @param page          (分页对象)
     * @param mustQuery     (必须满足条件)
     * @param mustNotQuery  (必须不满足条件)
     * @param shouldQuery   (可能满足条件)
     * @return
     */
    @Override
    public Page<Map<String, Object>> findAllAssociationByCondition(String indexName, String sortFieldName, String sortOrder, BasePageRequest page, MustQuery mustQuery, MustNotQuery mustNotQuery, ShouldQuery shouldQuery) {
        QueryBuilder query = QueryBuilders.boolQuery();
        if (mustQuery != null) {
            ((BoolQueryBuilder) query).must(QueryBuilders.termQuery(mustQuery.getFieldName(), mustQuery.getValue()));
        }
        if (mustNotQuery != null) {
            ((BoolQueryBuilder) query).mustNot(QueryBuilders.termQuery(mustNotQuery.getFieldName(), mustNotQuery.getValue()));
        }
        if (shouldQuery != null) {
            ((BoolQueryBuilder) query).should(QueryBuilders.termQuery(shouldQuery.getFieldName(), shouldQuery.getValue()));
        }
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(query)
                .setFetchSource(null, "data")
                .setFrom((page.getPage() - 1) * page.getSize())
                .setSize(page.getPage() * page.getSize());
        if (sortFieldName != null && !"".equals(sortFieldName) && sortOrder != null && !"".equals(sortOrder)) {
            searchRequestBuilder.addSort(sortFieldName, sortOrder.equalsIgnoreCase("DESC") ? SortOrder.DESC : SortOrder.ASC);
        }
        SearchResponse response = searchRequestBuilder.get();
        SearchHits hits = response.getHits();
        Long totalCount = hits.getTotalHits();
        Page<Map<String, Object>> pageResult = new Page<>(page.getPage(), page.getSize(), totalCount.intValue());
        pageResult.setData(getHitList(hits));
        return pageResult;
    }


    @Override
    public Map<String, Object> AggregationAll(String indexName, String searchFieldName, String templateName) {
        return null;
    }

    @Override
    public Double AggregationAllByCondition(String indexName, String searchFieldName, String templateName, AggregationEnum aggregationEnum) {
        return null;
    }
}
